/**
 * 
 */
package jp.tkym.labs.util.annotation;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


public class FindAnnotationInterpreter{
	
	private Map<Class<?>, AbstractAnnotationProcesser<?>> typeMap;
	private Map<Class<?>, AbstractAnnotationProcesser<?>> methodMap;
	private Map<Class<?>, AbstractAnnotationProcesser<?>> fieldMap;
	
	public FindAnnotationInterpreter(){
		typeMap = new HashMap<Class<?>, AbstractAnnotationProcesser<?>>();
		methodMap = new HashMap<Class<?>, AbstractAnnotationProcesser<?>>();
		fieldMap = new HashMap<Class<?>, AbstractAnnotationProcesser<?>>();
	}
	
	public <A extends Annotation> void typeProcesser(Class<A> cls, AbstractAnnotationProcesser<A> processer){
		typeMap.put(cls, processer);
	}
	
	public <A extends Annotation> void methodProcesser(Class<A> cls, AbstractAnnotationProcesser<A> processer){
		methodMap.put(cls, processer);
	}

	public <A extends Annotation> void fieldProcesser(Class<A> cls, AbstractAnnotationProcesser<A> processer){
		fieldMap.put(cls, processer);
	}

	public <T> void interpret(Class<T> cls){
		if(!typeMap.isEmpty()) findType(cls);
		if(!methodMap.isEmpty()) interpretMethod(cls);
		if(!fieldMap.isEmpty()) interpretField(cls);
	}
	
	private <T> void findType(Class<T> cls){
		List<Annotation> classList =  Arrays.asList(cls.getAnnotations());
		for(Annotation annotation : classList){
			processType(annotation);
		}
	}
	
	@SuppressWarnings("unchecked")
	private <A extends Annotation> void processType(A annotation){
		for(Class<?> clazz : typeMap.keySet()){
			if(clazz.isAssignableFrom(annotation.getClass())){
				AbstractAnnotationProcesser<A> processer = (AbstractAnnotationProcesser<A>)typeMap.get(clazz);
				processer.process(annotation);
			}
		}
	}
	
	private <T> void interpretMethod(Class<T> cls){
		List<Method> methodList = new ArrayList<Method>();  
		methodList.addAll(Arrays.asList(cls.getMethods()));
		for(Method method : methodList){
			List<Annotation> annotationList =  Arrays.asList(method.getAnnotations());
			for(Annotation annotation : annotationList){
				processMethod(method, annotation);
			}
		}
	}
	
	@SuppressWarnings("unchecked")
	private <A extends Annotation> void processMethod(Method method, A annotation){
		for(Class<?> clazz : methodMap.keySet()){
			if(clazz.isAssignableFrom(annotation.getClass())){
				AbstractAnnotationProcesser<A> processer = (AbstractAnnotationProcesser<A>)methodMap.get(clazz);
				processer.process(method, annotation);
			}
		}
	}
	
	private <T> void interpretField(Class<T> cls){
		List<Field> fieldList = new ArrayList<Field>();  
		fieldList.addAll(Arrays.asList(cls.getFields()));
		fieldList.addAll(Arrays.asList(cls.getDeclaredFields()));
		for(Field field : fieldList){
			List<Annotation> annotationList =  Arrays.asList(field.getAnnotations());
			for(Annotation annotation : annotationList){
				processField(field, annotation);
			}
		}
	}

	@SuppressWarnings("unchecked")
	private <A extends Annotation> void processField(Field field, A annotation){
		for(Class<?> clazz : fieldMap.keySet()){
			if(clazz.isAssignableFrom(annotation.getClass())){
				AbstractAnnotationProcesser<A> processer = (AbstractAnnotationProcesser<A>)fieldMap.get(clazz);
				processer.process(field, annotation);
			}
		}
	}
}